/*
 -- IOTA Crypto Core
 --
 -- 2018 by Thomas Pototschnig <microengineer18@gmail.com>
 -- discord: pmaxuw#8292
 -- https://gitlab.com/iccfpga-rv
 --
 -- Permission is hereby granted, free of charge, to any person obtaining
 -- a copy of this software and associated documentation files (the
 -- "Software"), to deal in the Software without restriction, including
 -- without limitation the rights to use, copy, modify, merge, publish,
 -- distribute, sublicense, and/or sell copies of the Software, and to
 -- permit persons to whom the Software is furnished to do so, subject to
 -- the following conditions:
 --
 -- The above copyright notice and this permission notice shall be
 -- included in all copies or substantial portions of the Software.
 --
 -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 -- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 -- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 -- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 -- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 -- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 -- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWAR
 */

#include <unistd.h>
#include <ArduinoJson.h>

#include "api/API.h"

#include "iota/transaction.h"
#include "iota/transfers.h"
#include "iota/bundle.h"

#include "utils.h"

#include "debugprintf.h"


namespace API {

	bool attachToTangle(Transaction* tx, const char* trunkTransaction, const char* branchTransaction, int minWeightMagnitude, int64_t timestamp, char* lastTXHash) {
		char prevTransaction[81];
		memcpy(prevTransaction, trunkTransaction, 81);

		bool first = true;
		do {
			if (first) {
				tx->setTrunkTransaction(trunkTransaction);
				tx->setBranchTransaction(branchTransaction);
				first = false;
			} else {
				tx->setTrunkTransaction(prevTransaction);
				tx->setBranchTransaction(trunkTransaction);
			}
			tx->setAttachmentTimestamp(timestamp);
			tx->setAttachmentTimestampLowerBound(0ll);
			tx->setAttachmentTimestampUpperBound(3812798742493ll);

			DoPoWResponse* resp = SBI::doPoW(tx->getRawPtr(), minWeightMagnitude);
			if (resp->isError())
				return false;

			tx->setNonce(resp->nonce);

			CurlHashResponse* resp2 = SBI::curlHash(tx->getRawPtr());
			//trace_printf("%s\n", resp2->hash);
			int mwmVerify = resp2->mwm;

			if (mwmVerify < minWeightMagnitude) {
				return false;
			}

			if (lastTXHash)
				memcpy(lastTXHash, &resp2->hash[0], 81);

			memcpy(prevTransaction, &resp2->hash[0], 81);
	//		trace_printf("%.*s\n", 81, prevTransaction);
			tx = tx->getNext();	//Prev(); //tx->getNext();
		} while (tx);

		return true;
	}


	int apiAttachToTangle() {
		if (
			!exists<const char*>(json, "trunkTransaction") ||
			!exists<const char*>(json, "branchTransaction") ||
			!exists<uint32_t>(json, "minWeightMagnitude") ||
			!exists<uint64_t>(json, "timestamp") ||
			!exists<JsonArray>(json, "trytes")
		) {
			return apiError(SBIError::MISSING_OR_INVALID);
		}

		const char* trunk = json["trunkTransaction"];
		if (!validateTrytes(trunk, 81)) {
			return apiError(SBIError::INVALID_TRUNK);
		}

		const char* branch = json["branchTransaction"];
		if (!validateTrytes(branch, 81)) {
			return apiError(SBIError::INVALID_BRANCH);
		}

		uint32_t mwm = json["minWeightMagnitude"];
		if (mwm < 3 || mwm > 14) {
			return apiError(SBIError::INVALID_MWM);
		}

		uint64_t timestamp = json["timestamp"];

		JsonArray trytes = json["trytes"];
		int numTrytes = (int) trytes.size();

		if (numTrytes > MAX_NUM_TRANSACTIONS) {
			return apiError(SBIError::TOO_MANY_TX);
		}
		if (numTrytes < 1) {
			return apiError(SBIError::TOO_FEW_TX);
		}

		for (int i = 0; i < numTrytes; i++) {
			if (!validateTrytes(trytes[i], 2673, false)) {
				return apiError(SBIError::INVALID_TRYTES);
			}
		}

		for (int i = 0; i < numTrytes; i++) {
			txs[i].init();
			if (i) {
				txs[i].setPrev(&txs[i - 1]);	// chain transactions
			}
			const char* t = trytes[i];
			memcpy(txs[i].getRawPtr(), t, 2673);
		}

		if (!attachToTangle(&txs[0], (char*) trunk, (char*) branch, mwm, timestamp, 0)) {
			return apiError(SBIError::ERROR);
		}
		json.clear();
		JsonArray trytesOut = json.createNestedArray("trytes");

		for (int i = 0; i < numTrytes; i++) {
			trytesOut.add(txs[i].getRawPtr());
		}
		return API_SUCCESS;
	}

	int apiDoPow() {
		if (
			!exists<JsonArray>(json, "trytes") ||
			!exists<uint32_t>(json, "minWeightMagnitude")
		) {
			return apiError(SBIError::MISSING_OR_INVALID);
		}

		JsonArray trytes = json["trytes"];
		int numTrytes = (int) trytes.size();

		uint32_t mwm = json["minWeightMagnitude"];

		if (mwm < 3 || mwm > 18) {
			return apiError(SBIError::INVALID_MWM);
		}

		if (numTrytes > MAX_NUM_TRANSACTIONS) {	// not more than 10TX at once
			return apiError(SBIError::TOO_MANY_TX);
		}
		if (numTrytes < 1) {
			return apiError(SBIError::TOO_FEW_TX);
		}


		for (int i = 0; i < numTrytes; i++) {
			if (!validateTrytes(trytes[i], 2673, false)) {
				return apiError(SBIError::INVALID_TRYTES);
			}
		}

		char nonce[MAX_NUM_TRANSACTIONS][27] = { 0 };
		for (int i = 0; i < numTrytes; i++) {
			const char* t = trytes[i];
			DoPoWResponse* resp = SBI::doPoW((char*) t, mwm);
			if (resp->isError())
				return apiError(resp->code);
			memcpy(&nonce[i][0], &resp->nonce[0], sizeof(nonce[i]));
		}

		json.clear();
		JsonArray trytesOut = json.createNestedArray("trytes");

		for (int i = 0; i < numTrytes; i++) {
			trytesOut.add(nonce[i]);
		}
		return API_SUCCESS;
	}


	int apiPowTestAverage() {
		txs[0].init();
		memset(txs[0].getRawPtr(), '9', sizeof(TX_t));

		for (int i=0;i<1000;i++) {
			DoPoWResponse* resp = SBI::doPoW(txs[0].getRawPtr(), 14);
			if (resp->isError())
				return apiError(resp->code);

			// change /somehow/ tx ... this is perfect :)
			txs[0].getsignatureMessageFragment()[i % 1024]++;
			// debugoutput holds the systick
			//debugPrintf("%4d %s\n", i, resp->nonce);
		}

		json.clear();
		json["powTestAverage"] = "pass";
		return API_SUCCESS;
	}

	bool registerAPIPoW() {
		return
				registerAPICall("doPow", &apiDoPow) &&
				registerAPICall("attachToTangle", &apiAttachToTangle) &&
				registerAPICall("powTestAverage", &apiPowTestAverage);
	}
}
